ledger-proto 0.1.0

Ledger hardware wallet protocol / APDU definitions
Documentation

Ledger Hardware Wallet APDU traits and shared types.

This provides abstractions for encoding and decoding APDUs for to support interaction with Ledger devices.

APDUs must implement [ApduBase] as well as [encdec::Encode] and [encdec::Decode] (or [encdec::DecodeOwned]) for binary serialisation, with commands providing header information via [ApduReq]. [encdec::Encode] and [encdec::Decode] can be automatically derived using encdec macros, or manually implemented over existing objects / encodings.

An [ApduStatic] helper is provided to automatically implement [ApduReq] for APDU requests with static headers and a common [ApduError] type is provided to unify serialisation and deserialisation errors across APDU objects.

Examples

Command APDU (no body) using [ApduStatic]:

use ledger_proto::{ApduStatic, ApduError, Encode, DecodeOwned};

/// Application information request APDU
#[derive(Clone, Debug, PartialEq, Encode, DecodeOwned)]
#[encdec(error = "ApduError")]
pub struct AppInfoReq {}

/// Set CLA and INS values for [AppInfoReq]
impl ApduStatic for AppInfoReq {
/// Application Info GET APDU is class `0xb0`
const CLA: u8 = 0xb0;
/// Application Info GET APDU is instruction `0x00`
const INS: u8 = 0x01;
}

Manual response APDU implementation

use ledger_proto::{ApduStatic, ApduError, Encode, Decode};

/// Example response APDU
#[derive(Clone, Debug, PartialEq)]
pub struct StringResp<'a> {
pub value: &'a str,
}

/// [Encode] implementation for [StringResp]
impl <'a> Encode for StringResp<'a> {
type Error = ApduError;

/// Fetch encoded length
fn encode_len(&self) -> Result<usize, Self::Error> {
Ok(1 + self.value.as_bytes().len())
}

/// Encode to bytes
fn encode(&self, buff: &mut [u8]) -> Result<usize, Self::Error> {
let b = self.value.as_bytes();

// Check buffer length is valid
if buff.len() < self.encode_len()?
|| b.len() > u8::MAX as usize {
return Err(ApduError::InvalidLength);
}

// Write value length
buff[0] = b.len() as u8;

// Write value
buff[1..][..b.len()]
.copy_from_slice(b);

Ok(1 + b.len())
}
}

impl <'a> Decode<'a> for StringResp<'a> {
type Output = Self;
type Error = ApduError;

fn decode(buff: &'a [u8]) -> Result<(Self::Output, usize), Self::Error> {
// Check buffer length
if buff.len() < 1 {
return Err(ApduError::InvalidLength);
}
let n = buff[0]as usize;
if n + 1 > buff.len() {
return Err(ApduError::InvalidLength);
}

// Parse string value
let s = match core::str::from_utf8(&buff[1..][..n]) {
Ok(v) => v,
Err(_) => return Err(ApduError::InvalidUtf8),
};

// Return object and parsed length
Ok((Self{ value: s}, n + 1))
}
}

For more examples, see the shared APDUs provided in the [apdus] module.